/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/**********************************************************************
   module: PCFX.C

   author: James R. Dose
   date:   April 1, 1994

   Low level routines to support PC sound effects created by Muse.

   (c) Copyright 1994 James R. Dose.  All Rights Reserved.
**********************************************************************/

#include <dos.h>
#include <stdlib.h>
#include <conio.h>
#include "dpmi.h"
#include "task_man.h"
#include "interrup.h"
#include "pcfx.h"

#define TRUE  ( 1 == 1 )
#define FALSE ( !TRUE )

static void PCFX_Service( task *Task );

static long   PCFX_LengthLeft;
static char  *PCFX_Sound = NULL;
static int    PCFX_LastSample;
static short  PCFX_Lookup[ 256 ];
static int    PCFX_UseLookupFlag = FALSE;
static int    PCFX_Priority;
static unsigned long PCFX_CallBackVal;
static void   ( *PCFX_CallBackFunc )( unsigned long ) = NULL;
static int    PCFX_TotalVolume = PCFX_MaxVolume;
static task  *PCFX_ServiceTask = NULL;
static int    PCFX_VoiceHandle = PCFX_MinVoiceHandle;

int PCFX_Installed = FALSE;

int PCFX_ErrorCode = PCFX_Ok;

#define PCFX_SetErrorCode( status ) \
   PCFX_ErrorCode   = ( status );


/*---------------------------------------------------------------------
   Function: PCFX_ErrorString

   Returns a pointer to the error message associated with an error
   number.  A -1 returns a pointer the current error.
---------------------------------------------------------------------*/

char *PCFX_ErrorString
   (
   int ErrorNumber
   )

   {
   char *ErrorString;

   switch( ErrorNumber )
      {
      case PCFX_Warning :
      case PCFX_Error :
         ErrorString = PCFX_ErrorString( PCFX_ErrorCode );
         break;

      case PCFX_Ok :
         ErrorString = "PC FX ok.";
         break;

      case PCFX_NoVoices :
         ErrorString = "No free voices available to PC FX.";
         break;

      case PCFX_VoiceNotFound :
         ErrorString = "No voice with matching handle found.";
         break;

      case PCFX_DPMI_Error :
         ErrorString = "DPMI Error in PCFX.";
         break;

      default :
         ErrorString = "Unknown PC FX error code.";
         break;
      }

   return( ErrorString );
   }


/**********************************************************************

   Memory locked functions:

**********************************************************************/


#define PCFX_LockStart PCFX_Stop


/*---------------------------------------------------------------------
   Function: PCFX_Stop

   Halts playback of the currently playing sound effect.
---------------------------------------------------------------------*/

int PCFX_Stop
   (
   int handle
   )

   {
   unsigned flags;

   if ( ( handle != PCFX_VoiceHandle ) || ( PCFX_Sound == NULL ) )
      {
      PCFX_SetErrorCode( PCFX_VoiceNotFound );
      return( PCFX_Warning );
      }

   flags = DisableInterrupts();

   // Turn off speaker
   outp( 0x61, inp( 0x61 ) & 0xfc );

   PCFX_Sound      = NULL;
   PCFX_LengthLeft = 0;
   PCFX_Priority   = 0;
   PCFX_LastSample = 0;

   RestoreInterrupts( flags );

   if ( PCFX_CallBackFunc )
      {
      PCFX_CallBackFunc( PCFX_CallBackVal );
      }

   return( PCFX_Ok );
   }


/*---------------------------------------------------------------------
   Function: PCFX_Service

   Task Manager routine to perform the playback of a sound effect.
---------------------------------------------------------------------*/

static void PCFX_Service
   (
   task *Task
   )

   {
   unsigned value;

   if ( PCFX_Sound )
      {
      if ( PCFX_UseLookupFlag )
         {
         value = PCFX_Lookup[ *PCFX_Sound ];
         PCFX_Sound++;
         }
      else
         {
         value = *( short int * )PCFX_Sound;
         PCFX_Sound += sizeof( short int );
         }

      if ( ( PCFX_TotalVolume > 0 ) && ( value != PCFX_LastSample ) )
         {
         PCFX_LastSample = value;
         if ( value )
            {
            outp( 0x43, 0xb6 );
            outp( 0x42, value );
            outp( 0x42, value >> 8 );
            outp( 0x61, inp( 0x61 ) | 0x3 );
            }
         else
            {
            outp( 0x61, inp( 0x61 ) & 0xfc );
            }
         }
      if ( --PCFX_LengthLeft == 0 )
         {
         PCFX_Stop( PCFX_VoiceHandle );
         }
      }
   }


/*---------------------------------------------------------------------
   Function: PCFX_VoiceAvailable

   Checks if a voice can be play at the specified priority.
---------------------------------------------------------------------*/

int PCFX_VoiceAvailable
   (
   int priority
   )

   {
   if ( priority < PCFX_Priority )
      {
      return( FALSE );
      }

   return( TRUE );
   }


/*---------------------------------------------------------------------
   Function: PCFX_Play

   Starts playback of a Muse sound effect.
---------------------------------------------------------------------*/

int PCFX_Play
   (
   PCSound *sound,
   int priority,
   unsigned long callbackval
   )

   {
   unsigned flags;

   if ( priority < PCFX_Priority )
      {
      PCFX_SetErrorCode( PCFX_NoVoices );
      return( PCFX_Warning );
      }

   PCFX_Stop( PCFX_VoiceHandle );

   PCFX_VoiceHandle++;
   if ( PCFX_VoiceHandle < PCFX_MinVoiceHandle )
      {
      PCFX_VoiceHandle = PCFX_MinVoiceHandle;
      }

   flags = DisableInterrupts();

   PCFX_LengthLeft = sound->length;

   if ( !PCFX_UseLookupFlag )
      {
      PCFX_LengthLeft >>= 1;
      }

   PCFX_Priority = priority;

   PCFX_Sound = &sound->data;
   PCFX_CallBackVal = callbackval;

   RestoreInterrupts( flags );

   return( PCFX_VoiceHandle );
   }


/*---------------------------------------------------------------------
   Function: PCFX_SoundPlaying

   Checks if a sound effect is currently playing.
---------------------------------------------------------------------*/

int PCFX_SoundPlaying
   (
   int handle
   )

   {
   int status;

   status = FALSE;
   if ( ( handle == PCFX_VoiceHandle ) && ( PCFX_LengthLeft > 0 ) )
      {
      status = TRUE;
      }

   return( status );
   }


/*---------------------------------------------------------------------
   Function: PCFX_SetTotalVolume

   Sets the total volume of the sound effects.
---------------------------------------------------------------------*/

int PCFX_SetTotalVolume
   (
   int volume
   )

   {
   unsigned flags;

   flags = DisableInterrupts();

   volume = max( volume, 0 );
   volume = min( volume, PCFX_MaxVolume );

   PCFX_TotalVolume = volume;

   if ( volume == 0 )
      {
      outp( 0x61, inp( 0x61 ) & 0xfc );
      }

   RestoreInterrupts( flags );

   return( PCFX_Ok );
   }


/*---------------------------------------------------------------------
   Function: PCFX_GetTotalVolume

   Returns the total volume of the sound effects.
---------------------------------------------------------------------*/

int PCFX_GetTotalVolume
   (
   void
   )

   {
   return( PCFX_TotalVolume );
   }


/*---------------------------------------------------------------------
   Function: PCFX_LockEnd

   Used for determining the length of the functions to lock in memory.
---------------------------------------------------------------------*/

static void PCFX_LockEnd
   (
   void
   )

   {
   }


/*---------------------------------------------------------------------
   Function: PCFX_UseLookup

   Sets up a pitch lookup table for PC sound effects.
---------------------------------------------------------------------*/

void PCFX_UseLookup
   (
   int use,
   unsigned value
   )

   {
   int pitch;
   int index;

   PCFX_Stop( PCFX_VoiceHandle );

   PCFX_UseLookupFlag = use;
   if ( use )
      {
      pitch = 0;
      for( index = 0; index < 256; index++ )
         {
         PCFX_Lookup[ index ] = pitch;
         pitch += value;
         }
      }
   }


/*---------------------------------------------------------------------
   Function: PCFX_SetCallBack

   Set the function to call when a voice stops.
---------------------------------------------------------------------*/

void PCFX_SetCallBack
   (
   void ( *function )( unsigned long )
   )

   {
   PCFX_CallBackFunc = function;
   }


/*---------------------------------------------------------------------
   Function: PCFX_Init

   Initializes the sound effect engine.
---------------------------------------------------------------------*/

int PCFX_Init
   (
   void
   )

   {
   int status;

   if ( PCFX_Installed )
      {
      PCFX_Shutdown();
      }

   status = PCFX_LockMemory();
   if ( status != PCFX_Ok )
      {
      PCFX_UnlockMemory();
      return( status );
      }

   PCFX_UseLookup( TRUE, 60 );
   PCFX_Stop( PCFX_VoiceHandle );
   PCFX_ServiceTask = TS_ScheduleTask( &PCFX_Service, 140, 2, NULL );
   TS_Dispatch();

   PCFX_CallBackFunc = NULL;
   PCFX_Installed = TRUE;

   PCFX_SetErrorCode( PCFX_Ok );
   return( PCFX_Ok );
   }


/*---------------------------------------------------------------------
   Function: PCFX_Shutdown

   Ends the use of the sound effect engine.
---------------------------------------------------------------------*/

int PCFX_Shutdown
   (
   void
   )

   {
   if ( PCFX_Installed )
      {
      PCFX_Stop( PCFX_VoiceHandle );
      TS_Terminate( PCFX_ServiceTask );
      PCFX_UnlockMemory();
      PCFX_Installed = FALSE;
      }

   PCFX_SetErrorCode( PCFX_Ok );
   return( PCFX_Ok );
   }


/*---------------------------------------------------------------------
   Function: PCFX_UnlockMemory

   Unlocks all neccessary data.
---------------------------------------------------------------------*/

void PCFX_UnlockMemory
   (
   void
   )

   {
   DPMI_UnlockMemoryRegion( PCFX_LockStart, PCFX_LockEnd );
   DPMI_Unlock( PCFX_LengthLeft );
   DPMI_Unlock( PCFX_Sound );
   DPMI_Unlock( PCFX_LastSample );
   DPMI_Unlock( PCFX_Lookup );
   DPMI_Unlock( PCFX_UseLookupFlag );
   DPMI_Unlock( PCFX_Priority );
   DPMI_Unlock( PCFX_CallBackVal );
   DPMI_Unlock( PCFX_CallBackFunc );
   DPMI_Unlock( PCFX_TotalVolume );
   DPMI_Unlock( PCFX_ServiceTask );
   DPMI_Unlock( PCFX_VoiceHandle );
   DPMI_Unlock( PCFX_Installed );
   DPMI_Unlock( PCFX_ErrorCode );
   }


/*---------------------------------------------------------------------
   Function: PCFX_LockMemory

   Locks all neccessary data.
---------------------------------------------------------------------*/

int PCFX_LockMemory
   (
   void
   )

   {
   int status;

   status  = DPMI_LockMemoryRegion( PCFX_LockStart, PCFX_LockEnd );
   status |= DPMI_Lock( PCFX_LengthLeft );
   status |= DPMI_Lock( PCFX_Sound );
   status |= DPMI_Lock( PCFX_LastSample );
   status |= DPMI_Lock( PCFX_Lookup );
   status |= DPMI_Lock( PCFX_UseLookupFlag );
   status |= DPMI_Lock( PCFX_Priority );
   status |= DPMI_Lock( PCFX_CallBackVal );
   status |= DPMI_Lock( PCFX_CallBackFunc );
   status |= DPMI_Lock( PCFX_TotalVolume );
   status |= DPMI_Lock( PCFX_ServiceTask );
   status |= DPMI_Lock( PCFX_VoiceHandle );
   status |= DPMI_Lock( PCFX_Installed );
   status |= DPMI_Lock( PCFX_ErrorCode );

   if ( status != DPMI_Ok )
      {
      PCFX_UnlockMemory();
      PCFX_SetErrorCode( PCFX_DPMI_Error );
      return( PCFX_Error );
      }

   return( PCFX_Ok );
   }
